﻿using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using mediaPrime.Logger;

namespace mediaPrime.Sdk
{
    /// <summary>
    /// This class handles all Extensions (load, unload, search for, etc.)
    /// </summary>
    internal class ExtensionManager
    {
        private string extensionsPath;
        private Version coreVersion;

        public ObservableCollection<BasePlugin> AllLoadedExtensions { get; set; }
        public ObservableCollection<Plugin> LoadedPlugins { get; set; }
        public ObservableCollection<BackgroundPlugin> LoadedBackgroundPlugins { get; set; }
        public ObservableCollection<SidePlugin> LoadedSidePlugins { get; set; }

        public ObservableCollection<ExtensionInfo> AllAvailableExtensions { get; set; }
        public ObservableCollection<ExtensionInfo> AvailablePlugins { get; set; }
        public ObservableCollection<ExtensionInfo> AvailableBackgroundPlugins { get; set; }
        public ObservableCollection<ExtensionInfo> AvailableSidePlugins { get; set; }
        public ObservableCollection<ExtensionInfo> AvailableAddons { get; set; }

        public event MessageEventHandler MessageSent;
        public event MessageWithResultEventHandler MessageWithResultSent;

        /// <summary>
        /// Initializes the Extension Manager
        /// </summary>
        /// <param name="extensionsPath">Path to the Extensions folder</param>
        /// <param name="coreVersion">Version number of mediaPrime.Core to only load supported Extensions</param>
        public ExtensionManager(string extensionsPath, Version coreVersion)
        {
            this.extensionsPath = extensionsPath;
            this.coreVersion = coreVersion;

            LoadedPlugins = new ObservableCollection<Plugin>();
            LoadedBackgroundPlugins = new ObservableCollection<BackgroundPlugin>();
            LoadedSidePlugins = new ObservableCollection<SidePlugin>();
            AllLoadedExtensions = new ObservableCollection<BasePlugin>();

            AvailablePlugins = new ObservableCollection<ExtensionInfo>();
            AvailableBackgroundPlugins = new ObservableCollection<ExtensionInfo>();
            AvailableSidePlugins = new ObservableCollection<ExtensionInfo>();
            AvailableAddons = new ObservableCollection<ExtensionInfo>();
            AllAvailableExtensions = new ObservableCollection<ExtensionInfo>();

            findPlugins();
        }


        /// <summary>
        /// Searches for all available plugins
        /// </summary>
        private void findPlugins()
        {
            if (System.IO.Directory.Exists(extensionsPath))
            {
                var extensionInfoFiles = System.IO.Directory.EnumerateFiles(extensionsPath, "mediaPrime.Extension.xml", System.IO.SearchOption.AllDirectories);

                foreach (var extensionInfoFile in extensionInfoFiles)
                {
                    try
                    {
                        using (var stream = new FileStream(extensionInfoFile, FileMode.Open, FileAccess.Read))
                        {
                            XmlSerializer serializer = new XmlSerializer(typeof(ExtensionInfo));
                            var extensionInfo = (ExtensionInfo)serializer.Deserialize(stream);

                            if (extensionInfo != null)
                            {
                                // Check for duplicate IDs                       
                                var duplicates = AllAvailableExtensions.Where(p => p.Id == extensionInfo.Id).Count();

                                if (duplicates > 0)
                                {
                                    Log.Error("An Extension with this ID already exists! ID: " + extensionInfo.Id);
                                    continue;
                                }

                                // Check for minimum supported version
                                var minimumCoreVersion = coreVersion;
                                Version.TryParse(extensionInfo.MinCoreVersion, out minimumCoreVersion);

                                if (coreVersion < minimumCoreVersion)
                                {
                                    Log.Warning("The Extension \"{0}\" requires a newer version of mediaPrime (current: {1}, required: {2})!", extensionInfo.Name, coreVersion.ToString(), minimumCoreVersion.ToString());
                                    continue;
                                }

                                // Set internal properties
                                var pluginDirectory = System.IO.Path.GetDirectoryName(extensionInfoFile);
                                var pluginAssemblyPath = System.IO.Path.Combine(pluginDirectory, extensionInfo.File);

                                extensionInfo.Directory = pluginDirectory;
                                extensionInfo.Path = pluginAssemblyPath;

                                AllAvailableExtensions.Add(extensionInfo);

                                // Add Plugin to collection of available Plugins
                                switch (extensionInfo.Type)
                                {
                                    case ExtensionType.Plugin:
                                        AvailablePlugins.Add(extensionInfo);
                                        break;
                                    case ExtensionType.BackgroundPlugin:
                                        AvailableBackgroundPlugins.Add(extensionInfo);
                                        break;
                                    case ExtensionType.SidePlugin:
                                        AvailableSidePlugins.Add(extensionInfo);
                                        break;
                                    case ExtensionType.Addon:
                                        AvailableAddons.Add(extensionInfo);
                                        break;
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Error while loading Extension info of \"{0}\": {1}", extensionInfoFile, ex.ToString());
                    }
                }
            }
            else
                Log.Error("Extension folder not found: " + extensionsPath);
        }

        private void messageSent(object sender, MessageEventArgs e)
        {
            var messageSent = MessageSent;
            if (messageSent != null)
                messageSent(sender, e);
        }

        private object messageWithResultSent(object sender, MessageEventArgs e)
        {
            var messageWithResultSent = MessageWithResultSent;
            if (messageWithResultSent != null)
                return messageWithResultSent(sender, e);
            else
                return null;
        }

        /// <summary>
        /// Loads a Plugin found by id
        /// </summary>
        /// <param name="id">Id of Plugin</param>
        /// <returns>Plugin</returns>
        public Plugin LoadPluginById(string id)
        {
            // Check if this Plugin is already loaded
            var alreadyLoadedPlugin = LoadedPlugins.Where(p => p.Id == id).FirstOrDefault();
            if (alreadyLoadedPlugin != null)
            {
                // Plugin already loaded!
                return alreadyLoadedPlugin;
            }

            // Search for Plugin with Plugin's id
            var pluginInfo = AvailablePlugins.Where(p => p.Id == id).FirstOrDefault();

            if (pluginInfo != null)
            {
                var plugin = LoadPlugin(pluginInfo.Path, id) as Plugin;
                if (plugin != null)
                {
                    LoadedPlugins.Add(plugin);
                    AllLoadedExtensions.Add(plugin);
                }

                return plugin;
            }
            else
            {
                Log.Error("Plugin not found: " + id);
                return null;
            }
        }

        /// <summary>
        /// Loads a Plugin
        /// </summary>
        /// <param name="pluginPath">Path to Plugin's assembly</param>
        /// <param name="id">Plugin's id</param>
        /// <returns>Plugin</returns>
        public BasePlugin LoadPlugin(string pluginPath, string id)
        {
            Log.Info("Loading Plugin: " + pluginPath);

            if (!System.IO.File.Exists(pluginPath))
            {
                Log.Error("Plugin not found: " + pluginPath);
                return null;
            }

            try
            {
                // Load Assembly
                Assembly assembly = Assembly.LoadFrom(pluginPath);
                if (assembly != null)
                {
                    // Search for PluginAttribute to get Plugin class
                    var attribute = assembly.GetCustomAttributes(typeof(PluginAttribute), true).FirstOrDefault() as PluginAttribute;
                    if (attribute != null)
                    {
                        var type = assembly.GetTypes().SingleOrDefault(t => t.Name == attribute.ClassName);

                        if (typeof(BasePlugin).IsAssignableFrom(type))
                        {
                            // Preinitialize Plugin's properties to make them already available in the Plugin's constructor
                            Plugin.initializing = true;
                            Plugin.preInitialize = (p) =>
                            {
                                p.Id = id;
                                p.Directory = System.IO.Path.GetDirectoryName(pluginPath);
                                p.MessageSent += messageSent;
                                p.MessageWithResultSent += messageWithResultSent;
                                Settings.LoadDefaultSettings(System.IO.Path.Combine(p.Directory, "DefaultSettings.xml"), id);
                                p.Settings = Settings.Apps[id];
                                p.availableAddons.AddRange(AvailableAddons.Where(a => a.AddonPluginDependency == id));

                            };

                            var plugin = Activator.CreateInstance(type) as BasePlugin;
                            if (plugin != null) // Found!  
                            {
                                Plugin.initializing = false;
                                Plugin.preInitialize = null;

                                return plugin;
                            }
                        }
                        else
                            Log.Debug("The class \"{0}\" specified by PluginAttribute is not derived from Plugin class!", attribute.ClassName);
                    }
                    else
                        Log.Debug("The PluginAttribute was not found for: " + pluginPath);
                }
                else
                    Log.Error("The following Plugin could not be loaded: " + pluginPath);
            }
            catch (Exception ex)
            {
                Log.Error("Error while loading the Plugin \"{0}\": {1}", pluginPath, ex.ToString());
            }

            return null;
        }



        /// <summary>
        /// Sends a message to a Plugin
        /// </summary>
        /// <param name="message">Message</param>
        public void SendMessage(Message message)
        {
            // Search for receiver
            var receiverFound = AllLoadedExtensions.Where(p => p.Id == message.Receiver).FirstOrDefault();
            if (receiverFound != null)
            {
                receiverFound.OnMessageReceived(message);
            }
        }

        public object SendMessageWithResult(Message message)
        {
            var receiverFound = AllLoadedExtensions.Where(p => p.Id == message.Receiver).FirstOrDefault();
            if (receiverFound != null)
            {
                return receiverFound.OnMessageWithResultReceived(message);
            }
            else
                return null;
        }

    }
}
